//Copyright (c) Microsoft Corporation.  All rights reserved.

#region

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Text;
using System.Windows.Forms;
using System.Xml;
using EnvDTE;
using EnvDTE80;
using Hmg.Tools.AssemblyReferences.Properties;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using VSLangProj80;

#endregion

namespace Hmg.Tools.AssemblyReferences
{
    /// <summary>
    /// </summary>
    //[ProvideToolboxControl("ToolWindowControl", false)]
    public partial class ToolWindowControl : UserControl
    {
        #region Fields

        private const string _attributeIncludeName = "Include";
        private const string _cobolProjectFileExtension = ".cobproj";
        private const string _configurationPath = @"\$(Configuration)\";
        private const string _csProjectFileExtension = ".csproj";
        private const string _debugPath = @"\debug\";
        private const string _hintPathNodeName = "HintPath";
        private const string _embedInteropTypesNodeName = "EmbedInteropTypes";
        private const string _msbuildNamespaceUri = "http://schemas.microsoft.com/developer/msbuild/2003";
        private const string _namespaceShortCut = "ab";
        private const string _privateNodeName = "Private";
        private const string _referenceNodeName = "Reference";
        private const string _releasePath = @"\release\";
        private const string _specificVersionNodeName = "SpecificVersion";
        private const string _systemDllName = "System";
        private const string _vbProjectFileExtension = ".vbproj";
        private const string _xpathProjectReferences = "//ab:ItemGroup/ab:ProjectReference";
        private const string _xpathReferences = "//ab:ItemGroup/ab:Reference";
        private readonly Dictionary<string, XmlDocumentHolder> _needsToBeSaved = new Dictionary<string, XmlDocumentHolder>();
        private DTE2 _applicationObject;
        private CommandEvents _commandEvents;
        private DocumentEvents _documentEvents;
        private SolutionEvents _solutionEvents;
        private SourceControl _sourceControl;

        #endregion

        #region Constructor

        /// <summary>
        ///   Initializes a new instance of the <see cref = "ToolWindowControl" /> class.
        /// </summary>
        public ToolWindowControl()
        {
            InitializeComponent();
        }

        #endregion

        #region Properties

        /// <summary>
        ///   Gets or sets the application object.
        /// </summary>
        /// <value>The application object.</value>
        public DTE2 ApplicationObject
        {
            get
            {
                return _applicationObject;
            }
            set
            {
                _applicationObject = value;
                if (_applicationObject != null)
                {
                    _sourceControl = _applicationObject.SourceControl;

                    Events events = _applicationObject.Events;
                    if (events != null)
                    {
                        _documentEvents = events.get_DocumentEvents(null);
                        _commandEvents = events.get_CommandEvents("{00000000-0000-0000-0000-000000000000}", 0);
                    }
                    ReadAllReferences();
                }

                RegisterEvents();
            }
        }

        /// <summary>
        ///   Gets or sets the parent tool window.
        /// </summary>
        /// <value>The parent tool window.</value>
        public ToolWindowPane ParentToolWindow { get; set; }

        #endregion

        #region Static Methods

        /// <summary>
        ///   Gets the assembly reference.
        /// </summary>
        /// <param name = "item">The item.</param>
        private static Reference3 GetAssemblyReference(ListViewItem item)
        {
            AssemblyReferenceInformation assemblyReferenceInformation = (AssemblyReferenceInformation)item.Tag;
            if (assemblyReferenceInformation != null)
            {
                return assemblyReferenceInformation.AssemblyReference;
            }
            return null;
        }

        /// <summary>
        ///   Gets the directory to replace.
        /// </summary>
        /// <param name = "assemblyPath">The assembly path.</param>
        /// <param name = "directoryName">Name of the directory.</param>
        /// <returns></returns>
        private static string GetDirectoryToReplace(string assemblyPath, string directoryName)
        {
            int posDirectory = assemblyPath.IndexOf(directoryName, StringComparison.InvariantCultureIgnoreCase);
            if (posDirectory > -1)
            {
                return assemblyPath.Substring(posDirectory, directoryName.Length);
            }
            return string.Empty;
        }

        /// <summary>
        ///   Gets the hint node.
        /// </summary>
        /// <param name = "referenceNode">The reference node.</param>
        /// <param name = "namespaceManager">The namespace manager.</param>
        /// <returns></returns>
        private static XmlNode GetHintNode(XmlNode referenceNode, XmlNamespaceManager namespaceManager)
        {
            // try to get the name node
            string xPath = string.Format(CultureInfo.InvariantCulture, "{0}:{1}", _namespaceShortCut, _hintPathNodeName);
            return referenceNode.SelectSingleNode(xPath, namespaceManager);
        }

        /// <summary>
        ///   Gets the hint node from project reference.
        /// </summary>
        /// <param name = "assemblyReference">The assembly reference.</param>
        /// <param name = "documentHolder">The document holder.</param>
        /// <param name = "projectReferenceNode">The project reference node.</param>
        /// <param name = "referenceNode">The reference node.</param>
        /// <returns></returns>
        private static XmlNode GetHintNodeFromProjectReference(Reference3 assemblyReference, XmlDocumentHolder documentHolder, XmlNode projectReferenceNode, XmlNode referenceNode)
        {
            string containingProjectFullName = assemblyReference.ContainingProject.FullName;
            string assemblyPath = assemblyReference.Path;

            //<Reference Include="HMG.Core, Version=1.0.0.24459, Culture=neutral, processorArchitecture=MSIL">
            //  <SpecificVersion>False</SpecificVersion>
            //  <HintPath>..\..\..\..\..\..\Build\Bin\$(Configuration)\HMG.Core.dll</HintPath>
            //  <Private>True</Private>
            //</Reference>
            if (referenceNode.Attributes != null)
            {
                XmlAttribute includeAttribute = referenceNode.Attributes.Append(documentHolder.XmlDocument.CreateAttribute(_attributeIncludeName));

                includeAttribute.InnerText = GetReferenceIncludeText(assemblyReference);
            }

            if (projectReferenceNode.OwnerDocument != null)
            {
                XmlNode hintNode = projectReferenceNode.OwnerDocument.CreateNode(XmlNodeType.Element, _hintPathNodeName, _msbuildNamespaceUri);

                // Make the path relative
                string relativeAssemblyPath = GetRelativeAssemblyPath(assemblyPath, containingProjectFullName);

                string newAssemblyPath;
                ReplaceDebugReleaseAgainstConfigurationPath(relativeAssemblyPath, out newAssemblyPath);
                SetHintPath(hintNode, newAssemblyPath);
                return hintNode;
            }
            throw new InvalidOperationException("Hintpath node not found.");
        }


        /// <summary>
        ///   Gets the EmbedInteropType node.
        /// </summary>
        /// <param name = "referenceNode">The reference node.</param>
        /// <param name = "namespaceManager">The namespace manager.</param>
        /// <returns></returns>
        private static XmlNode GetEmbedNode(XmlNode referenceNode, XmlNamespaceManager namespaceManager)
        {
            string xPath = string.Format(CultureInfo.InvariantCulture, "{0}:{1}", _namespaceShortCut, _embedInteropTypesNodeName);
            return referenceNode.SelectSingleNode(xPath, namespaceManager);
        }


        /// <summary>
        ///   Gets the private node.
        /// </summary>
        /// <param name = "documentHolder">The document holder.</param>
        /// <returns></returns>
        private static XmlNode GetPrivateNode(XmlDocumentHolder documentHolder)
        {
            XmlNode privateNode = documentHolder.XmlDocument.CreateNode(XmlNodeType.Element, _privateNodeName, _msbuildNamespaceUri);
            privateNode.InnerText = "True";
            return privateNode;
        }

        /// <summary>
        ///   Gets the assembly reference node.
        /// </summary>
        /// <param name = "item">The item.</param>
        /// <returns></returns>
        private static XmlNode GetProjectFileReferenceNode(ListViewItem item)
        {
            AssemblyReferenceInformation assemblyReferenceInformation = (AssemblyReferenceInformation)item.Tag;
            if (assemblyReferenceInformation != null)
            {
                return assemblyReferenceInformation.ProjectFileReferenceNode;
            }
            return null;
        }

        /// <summary>
        ///   Gets the reference include text.
        /// </summary>
        /// <param name = "assemblyReference">The assembly reference.</param>
        /// <returns></returns>
        private static string GetReferenceIncludeText(Reference3 assemblyReference)
        {
            StringBuilder attributeBuilder = new StringBuilder();
            attributeBuilder.AppendFormat(CultureInfo.InvariantCulture, "{0}, Version={1}", assemblyReference.Name, assemblyReference.Version);
            // add assembly culture info
            if (string.IsNullOrEmpty(assemblyReference.Culture) || (assemblyReference.Culture == "0"))
            {
                attributeBuilder.Append(", Culture=neutral");
            }
            else
            {
                attributeBuilder.AppendFormat(CultureInfo.InvariantCulture, ", Culture={0}", assemblyReference.Culture);
            }
            // add public key token
            if (assemblyReference.PublicKeyToken != null)
            {
                attributeBuilder.AppendFormat(CultureInfo.InvariantCulture, ", PublicKeyToken={0}", assemblyReference.PublicKeyToken);
            }

            return attributeBuilder.ToString();
        }

        /// <summary>
        ///   Gets the name of the reference out of the file.
        ///   The file is represented by the XmlNode.
        /// </summary>
        /// <param name = "parentNode">The parent node.</param>
        /// <param name = "namespaceManager">The namespace manager.</param>
        /// <param name = "namespaceShortcut">The namespace shortcut.</param>
        /// <returns></returns>
        private static string GetReferenceName(XmlNode parentNode, XmlNamespaceManager namespaceManager, string namespaceShortcut)
        {
            // try to get the name node
            string xPath = string.Format(CultureInfo.InvariantCulture, "{0}:Name", namespaceShortcut);
            XmlNode node = parentNode.SelectSingleNode(xPath, namespaceManager);

            if (node != null)
            {
                return node.InnerText;
            }

            if (parentNode.Attributes != null)
            {
                string includeAttribute = parentNode.Attributes.GetNamedItem("Include").InnerText;

                int commaPosition = includeAttribute.IndexOf(',');
                if (commaPosition > -1)
                {
                    return includeAttribute.Remove(commaPosition);
                }
                return includeAttribute;
            }
            throw new InvalidOperationException("Reference name not found.");
        }

        /// <summary>
        ///   Gets the relative assembly path.
        /// </summary>
        /// <param name = "assemblyPath">The assembly path.</param>
        /// <param name = "containingProjectFullName">Full name of the containing project.</param>
        /// <returns></returns>
        private static string GetRelativeAssemblyPath(string assemblyPath, string containingProjectFullName)
        {
            Uri uriAssemblyPath = new Uri(assemblyPath);
            Uri containingProjectPathUri = new Uri(containingProjectFullName);
            Uri uriRelativeAssemblyPath = containingProjectPathUri.MakeRelativeUri(uriAssemblyPath);
            // Change the path in a way, visual studio would have created it.
            return uriRelativeAssemblyPath.ToString().Replace('/', '\\');
        }

        /// <summary>
        ///   Gets the specific version node.
        /// </summary>
        /// <param name = "documentHolder">The document holder.</param>
        /// <returns></returns>
        private static XmlNode GetSpecificVersionNode(XmlDocumentHolder documentHolder)
        {
            XmlNode specificVersionNode = documentHolder.XmlDocument.CreateNode(XmlNodeType.Element, _specificVersionNodeName, _msbuildNamespaceUri);
            specificVersionNode.InnerText = "false";
            return specificVersionNode;
        }

        /// <summary>
        ///   Gets the XML document holder.
        /// </summary>
        /// <param name = "item">The item.</param>
        /// <returns></returns>
        private static XmlDocumentHolder GetXmlDocumentHolder(ListViewItem item)
        {
            AssemblyReferenceInformation assemblyReferenceInformation = (AssemblyReferenceInformation)item.Tag;
            if (assemblyReferenceInformation != null)
            {
                return assemblyReferenceInformation.DocumentHolder;
            }
            return null;
        }

        /// <summary>
        ///   Gets the XML namespace manager.
        /// </summary>
        /// <param name = "item">The item.</param>
        /// <returns></returns>
        private static XmlNamespaceManager GetXmlNamespaceManager(ListViewItem item)
        {
            AssemblyReferenceInformation assemblyReferenceInformation = (AssemblyReferenceInformation)item.Tag;
            return (assemblyReferenceInformation != null) ? assemblyReferenceInformation.XmlNamespaceManager : null;
        }

        /// <summary>
        ///   Replaces debug or release against the configuration path.
        /// </summary>
        /// <param name = "assemblyPath">The assembly path.</param>
        /// <param name = "newAssemblyPath">The new assembly path.</param>
        /// <returns>true, if it replace a part of the path</returns>
        private static bool ReplaceDebugReleaseAgainstConfigurationPath(string assemblyPath, out string newAssemblyPath)
        {
            // check, if there is debug or release in the assembly path
            string directoryToReplace = GetDirectoryToReplace(assemblyPath, _debugPath);
            if (string.IsNullOrEmpty(directoryToReplace))
            {
                directoryToReplace = GetDirectoryToReplace(assemblyPath, _releasePath);
            }

            if (!string.IsNullOrEmpty(directoryToReplace))
            {
                newAssemblyPath = assemblyPath.Replace(directoryToReplace, _configurationPath);
                return true;
            }
            newAssemblyPath = assemblyPath;
            return false;
        }

        /// <summary>
        ///   Sets the hint path.
        /// </summary>
        /// <param name = "hintNode">The hint node.</param>
        /// <param name = "assemblyPath">The assembly path.</param>
        private static void SetHintPath(XmlNode hintNode, string assemblyPath)
        {
            hintNode.InnerText = assemblyPath;
        }

        /// <summary>
        ///   Sets the item appearance.
        /// </summary>
        /// <param name = "item">The item.</param>
        /// <param name = "icon">The icon.</param>
        private static void SetItemAppearance(ListViewItem item, ReferenceIcon icon)
        {
            switch (icon)
            {
                case ReferenceIcon.CanNotChange:
                    item.ImageIndex = 6;
                    item.ForeColor = Color.Gray;
                    item.ToolTipText = LocalResources.ReferenceIsFineAndOk;
                    break;
                case ReferenceIcon.Error:
                    item.ImageIndex = 5;
                    item.ForeColor = Color.IndianRed;
                    item.ToolTipText = LocalResources.ReferenceNeedsToBeFixed;
                    break;
                case ReferenceIcon.Ok:
                    item.ImageIndex = 6;
                    item.ToolTipText = LocalResources.ReferenceOk;
                    break;
                case ReferenceIcon.NotFound:
                    item.ImageIndex = 5;
                    item.ToolTipText = LocalResources.UnresolvedReference;
                    item.ForeColor = Color.IndianRed;
                    break;
                case ReferenceIcon.CSharpProject:
                    item.ImageIndex = 7;
                    item.ToolTipText = LocalResources.CSharpProjectReference;
                    break;

                case ReferenceIcon.VisualBasicProject:
                    item.ImageIndex = 8;
                    item.ToolTipText = LocalResources.VBProjectReference;
                    break;

                case ReferenceIcon.CppProject:
                    item.ImageIndex = 6;
                    item.ToolTipText = LocalResources.CPPProjectReference;
                    break;

                default:
                    throw new NotImplementedException();
            }
        }

        /// <summary>
        ///   Shows the message.
        /// </summary>
        /// <param name = "ex">The ex.</param>
        /// <returns></returns>
        [Conditional("DEBUG")]
        private static void ShowMessage(Exception ex)
        {
            MessageBox.Show(string.Format(CultureInfo.InvariantCulture, "Error in Hmg.Tools.AssemblyReferences Addin: {0}", ex));
        }

        #endregion

        #region Button and context menue events

        /// <summary>
        ///   Handles the Opening event of the _contextMenuStrip control.
        /// </summary>
        /// <param name = "sender">The source of the event.</param>
        /// <param name = "e">The <see cref = "System.ComponentModel.CancelEventArgs" /> instance containing the event data.</param>
        private void ContextMenuStripOpening(object sender, CancelEventArgs e)
        {
            EnableAllToolStripMenuItems(true);

            _toolStripMenuItemCopyLocal.CheckState = CheckState.Unchecked;
            _toolStripMenuItemSpecificVersion.CheckState = CheckState.Unchecked;

            bool firstAssemblyReference = true;
            foreach (ListViewItem item in _listViewHints.SelectedItems)
            {
                Reference3 assemblyReference = GetAssemblyReference(item);

                if (assemblyReference != null)
                {
                    bool isSystemDll = assemblyReference.Name.StartsWith(_systemDllName);
                    bool isProjectReference = (assemblyReference.SourceProject != null);

                    _toolStripMenuItemCopyLocal.Enabled &= !isSystemDll;
                    _toolStripMenuItemSpecificVersion.Enabled &= (!isSystemDll && !isProjectReference);
                    _toolStripMenuItemChangeProjectReferenceToAssemblyReference.Enabled &= isProjectReference;

                    if (firstAssemblyReference)
                    {
                        // initially set the two flags
                        _toolStripMenuItemCopyLocal.Checked = assemblyReference.CopyLocal;
                        _toolStripMenuItemSpecificVersion.Checked = assemblyReference.SpecificVersion;
                        firstAssemblyReference = false;
                    }
                    else
                    {
                        if (_toolStripMenuItemCopyLocal.Checked != assemblyReference.CopyLocal)
                        {
                            _toolStripMenuItemCopyLocal.CheckState = CheckState.Indeterminate;
                        }
                        if (_toolStripMenuItemSpecificVersion.Checked != assemblyReference.SpecificVersion)
                        {
                            _toolStripMenuItemSpecificVersion.CheckState = CheckState.Indeterminate;
                        }
                    }

                    bool editHintPathEnabled = false;
                    if (_listViewHints.SelectedItems.Count == 1)
                    {
                        XmlNode projectFileReferenceNode = GetProjectFileReferenceNode(item);
                        if (projectFileReferenceNode != null)
                        {
                            XmlNamespaceManager xmlNamespaceManager = GetXmlNamespaceManager(item);
                            string referenceName = GetReferenceName(projectFileReferenceNode, xmlNamespaceManager, _namespaceShortCut);

                            editHintPathEnabled = (!referenceName.StartsWith(_systemDllName) && !isProjectReference);
                        }
                    }
                    _toolStripMenuItemEditHintPath.Enabled = editHintPathEnabled;
                }
                else
                {
                    EnableAllToolStripMenuItems(false);
                }
            }
        }

        /// <summary>
        ///   Handles the Click event of the _toolStripMenuItemCopyLocal control.
        /// </summary>
        /// <param name = "sender">The source of the event.</param>
        /// <param name = "e">The <see cref = "System.EventArgs" /> instance containing the event data.</param>
        private void ToolStripMenuItemCopyLocalClick(object sender, EventArgs e)
        {
            foreach (ListViewItem item in _listViewHints.SelectedItems)
            {
                Reference3 assemblyReference = GetAssemblyReference(item);
                if (assemblyReference != null)
                {
                    assemblyReference.CopyLocal = !assemblyReference.CopyLocal;
                    item.SubItems[columnCopyLocal.DisplayIndex].Text = assemblyReference.CopyLocal.ToString();
                }
            }
        }

        /// <summary>
        ///   Handles the Click event of the _toolStripMenuItemSpecificVersion control.
        /// </summary>
        /// <param name = "sender">The source of the event.</param>
        /// <param name = "e">The <see cref = "System.EventArgs" /> instance containing the event data.</param>
        private void ToolStripMenuItemSpecificVersionClick(object sender, EventArgs e)
        {
            foreach (ListViewItem item in _listViewHints.SelectedItems)
            {
                Reference3 assemblyReference = GetAssemblyReference(item);
                if (assemblyReference != null)
                {
                    assemblyReference.SpecificVersion = !assemblyReference.SpecificVersion;

                    item.SubItems[columnSpecificVersion.DisplayIndex].Text = assemblyReference.SpecificVersion.ToString();
                }
            }
        }

        /// <summary>
        ///   Handles the Click event of the _toolStripMenuItemaddHintPath control.
        /// </summary>
        /// <param name = "sender">The source of the event.</param>
        /// <param name = "e">The <see cref = "System.EventArgs" /> instance containing the event data.</param>
        private void ToolStripMenuItemaddHintPathClick(object sender, EventArgs e)
        {
            if (_listViewHints.SelectedItems.Count == 1)
            {
                ListViewItem item = _listViewHints.SelectedItems[0];
                XmlNode projectFileReferenceNode = GetProjectFileReferenceNode(item);
                XmlDocumentHolder documentHolder = GetXmlDocumentHolder(item);
                Reference3 assemblyReference = GetAssemblyReference(item);

                if ((projectFileReferenceNode != null) && (documentHolder != null) && (assemblyReference != null))
                {
                    using (OpenFileDialog openFileDialog = new OpenFileDialog())
                    {
                        openFileDialog.Multiselect = false;
                        openFileDialog.CheckFileExists = true;
                        openFileDialog.DefaultExt = "dll";
                        openFileDialog.FileName = string.Format(CultureInfo.InvariantCulture, "{0}.dll", assemblyReference.Name);
                        openFileDialog.Filter = string.Format(CultureInfo.InvariantCulture, "Assembly ({0})|{0}", openFileDialog.FileName);
                        openFileDialog.InitialDirectory = assemblyReference.Path;
                        FileInfo projectFileInfo = new FileInfo(assemblyReference.ContainingProject.FullName);

                        openFileDialog.InitialDirectory = projectFileInfo.DirectoryName;

                        if (openFileDialog.ShowDialog() == DialogResult.OK)
                        {
                            bool newHintNodeCreated = false;

                            // Try to get the hint path from the project
                            XmlNode hintNode = GetHintNode(projectFileReferenceNode, GetXmlNamespaceManager(item));

                            if (hintNode == null)
                            {
                                newHintNodeCreated = true;
                                // Create a new one, if it doesn't exist.
                                if (projectFileReferenceNode.OwnerDocument == null)
                                {
                                    throw new InvalidOperationException("projectFileReferenceNode.OwnerDocument is null");
                                }
                                hintNode = projectFileReferenceNode.OwnerDocument.CreateNode(XmlNodeType.Element, _hintPathNodeName, _msbuildNamespaceUri);
                            }

                            // make path relative to the project
                            string relativeAssemblyPath = GetRelativeAssemblyPath(openFileDialog.FileName,
                                                                                  assemblyReference.ContainingProject.
                                                                                      FullName);

                            string assemblyPath;
                            ReplaceDebugReleaseAgainstConfigurationPath(relativeAssemblyPath, out assemblyPath);
                            SetHintPath(hintNode, assemblyPath);
                            if (newHintNodeCreated)
                            {
                                projectFileReferenceNode.AppendChild(hintNode);
                            }
                            SaveProject(documentHolder);
                        }
                    }
                }
            }
        }


        /// <summary>
        ///   Handles the Click event of the _toolStripMenuItemEmbedInteropTypes control.
        /// </summary>
        /// <param name = "sender">The source of the event.</param>
        /// <param name = "e">The <see cref = "System.EventArgs" /> instance containing the event data.</param>
        private void ToolStripMenuItemEmbedInteropClick(object sender, EventArgs e)
        {
            if (_listViewHints.SelectedItems.Count != 0)
            {
                foreach (ListViewItem item in _listViewHints.SelectedItems)
                {
                    XmlNode projectFileReferenceNode = GetProjectFileReferenceNode(item);
                    XmlDocumentHolder documentHolder = GetXmlDocumentHolder(item);
                    Reference3 assemblyReference = GetAssemblyReference(item);

                    if ((projectFileReferenceNode != null) && (documentHolder != null) && (assemblyReference != null))
                    {
                        bool newEmbedNodeCreated = false;

                        // Try to get the embed value from the project
                        XmlNode embedNode = GetEmbedNode(projectFileReferenceNode, GetXmlNamespaceManager(item));

                        if (embedNode == null)
                        {
                            newEmbedNodeCreated = true;
                            // Create a new one, if it doesn't exist.
                            if (projectFileReferenceNode.OwnerDocument == null)
                            {
                                throw new InvalidOperationException("projectFileReferenceNode.OwnerDocument is null");
                            }
                            embedNode = projectFileReferenceNode.OwnerDocument.CreateNode(XmlNodeType.Element, _embedInteropTypesNodeName, _msbuildNamespaceUri);
                            embedNode.InnerText = "False";
                        }

                        if (newEmbedNodeCreated)
                        {
                            projectFileReferenceNode.AppendChild(embedNode);
                        }

                        if (embedNode.InnerText == "False")
                        {
                            embedNode.InnerText = "True";
                        }
                        else
                        {
                            embedNode.InnerText = "False";
                        }

                        SaveProject(documentHolder);
                    }
                }
            }
        }


        /// <summary>
        ///   Handles the Click event of the _toolStripMenuItemchangeProjectReferenceToAssemblyReference control.
        /// </summary>
        /// <param name = "sender">The source of the event.</param>
        /// <param name = "e">The <see cref = "System.EventArgs" /> instance containing the event data.</param>
        private void ToolStripMenuItemchangeProjectReferenceToAssemblyReferenceClick(object sender, EventArgs e)
        {
            foreach (ListViewItem item in _listViewHints.SelectedItems)
            {
                XmlNode projectReferenceNode = GetProjectFileReferenceNode(item);
                XmlDocumentHolder documentHolder = GetXmlDocumentHolder(item);
                Reference3 assemblyReference = GetAssemblyReference(item);
                XmlNamespaceManager xmlNamespaceManager = GetXmlNamespaceManager(item);

                if ((projectReferenceNode != null) && (documentHolder != null) && (assemblyReference != null) && (xmlNamespaceManager != null))
                {
                    XmlNode referenceNode = documentHolder.XmlDocument.CreateNode(XmlNodeType.Element, _referenceNodeName, _msbuildNamespaceUri);

                    referenceNode.AppendChild(GetHintNodeFromProjectReference(assemblyReference, documentHolder, projectReferenceNode, referenceNode));
                    referenceNode.AppendChild(GetPrivateNode(documentHolder));
                    referenceNode.AppendChild(GetSpecificVersionNode(documentHolder));

                    XmlNode firstReferenceNode = documentHolder.XmlDocument.SelectSingleNode(_xpathReferences, xmlNamespaceManager);
                    if ((firstReferenceNode != null) && (firstReferenceNode.ParentNode != null))
                    {
                        firstReferenceNode.ParentNode.AppendChild(referenceNode);
                    }

                    // remove the project reference
                    XmlNode itemGroupNode = projectReferenceNode.ParentNode;
                    if (itemGroupNode != null)
                    {
                        itemGroupNode.RemoveChild(projectReferenceNode);

                        // if the parent node is empty, remove it too
                        if ((itemGroupNode.FirstChild == null) && (itemGroupNode.ParentNode != null))
                        {
                            itemGroupNode.ParentNode.RemoveChild(itemGroupNode);
                        }
                    }
                    SaveProject(documentHolder);
                }
            }
        }

        /// <summary>
        ///   Handles the Click event of the deleteToolStripMenuItem control.
        /// </summary>
        /// <param name = "sender">The source of the event.</param>
        /// <param name = "e">The <see cref = "System.EventArgs" /> instance containing the event data.</param>
        private void DeleteToolStripMenuItemClick(object sender, EventArgs e)
        {
            foreach (ListViewItem item in _listViewHints.SelectedItems)
            {
                Reference3 assemblyReference = GetAssemblyReference(item);
                if (assemblyReference != null)
                {
                    assemblyReference.Remove();
                }
            }
            foreach (ListViewItem item in _listViewHints.SelectedItems)
            {
                _listViewHints.Items.Remove(item);
            }
        }

        /// <summary>
        ///   Handles the Click event of the refreshToolStripButton control.
        /// </summary>
        /// <param name = "sender">The source of the event.</param>
        /// <param name = "e">The <see cref = "System.EventArgs" /> instance containing the event data.</param>
        private void RefreshToolStripButtonClick(object sender, EventArgs e)
        {
            ReadAllReferences();
        }

        /// <summary>
        ///   Handles the Click event of the saveToolStripButton control.
        /// </summary>
        /// <param name = "sender">The source of the event.</param>
        /// <param name = "e">The <see cref = "System.EventArgs" /> instance containing the event data.</param>
        private void SaveToolStripButtonClick(object sender, EventArgs e)
        {
            SaveDirtyProjects();
        }

        /// <summary>
        ///   Handles the CheckedChanged event of the toolStripButtonShowAll control.
        /// </summary>
        /// <param name = "sender">The source of the event.</param>
        /// <param name = "e">The <see cref = "System.EventArgs" /> instance containing the event data.</param>
        private void ToolStripButtonShowAllCheckedChanged(object sender, EventArgs e)
        {
            Settings.Default.ShowAllReferences = !Settings.Default.ShowAllReferences;
            Settings.Default.Save();
            ReadAllReferences();
        }

        #endregion

        #region Public Methods

        /// <summary>
        ///   Unregisters the events.
        /// </summary>
        public void UnregisterEvents()
        {
            // unregister events
            _solutionEvents.Opened -= SolutionEventsOpened;
            _solutionEvents.ProjectAdded -= SolutionEventsProjectAdded;
            _solutionEvents.ProjectRemoved -= SolutionEventsProjectRemoved;
            _solutionEvents.ProjectRenamed -= SolutionEventsProjectRenamed;
            _solutionEvents.AfterClosing -= SolutionEventsAfterClosing;
            _documentEvents.DocumentSaved -= DocumentEventsDocumentSaved;

            _commandEvents.AfterExecute -= CommandEventsAfterExecute;
        }

        #endregion

        #region Private Methods

        /// <summary>
        ///   Adds the assemblies to list view.
        /// </summary>
        /// <param name = "documentHolder">The document holder.</param>
        /// <param name = "xmlNamespaceManager">The XML namespace manager.</param>
        /// <param name = "projectFullName">Full name of the project.</param>
        /// <param name = "projectGroup">The project group.</param>
        /// <param name = "referenceNodelist">The reference nodelist.</param>
        /// <param name = "visualStudioProject">The visual studio project.</param>
        private void AddAssembliesToListView(XmlDocumentHolder documentHolder, XmlNamespaceManager xmlNamespaceManager, string projectFullName, ListViewGroup projectGroup,
                                             XmlNodeList referenceNodelist, VSProject2 visualStudioProject)
        {
            foreach (XmlNode referenceNode in referenceNodelist)
            {
                ReferenceIcon referenceIcon = ReferenceIcon.Ok;
                string referenceName = GetReferenceName(referenceNode, xmlNamespaceManager, _namespaceShortCut);

                // Try to find the corresponding visual studio api reference to get the resolved path and some other properties
                Reference3 assemblyReference = null;
                if (visualStudioProject != null)
                {
                    assemblyReference = (Reference3)visualStudioProject.References.Find(referenceName);

                    // Try to find it by 'name'. The Find method searches by identity and this is not working for project references
                    if (assemblyReference == null)
                    {
                        for (int index = 0; index < visualStudioProject.References.Count; index++)
                        {
                            Reference3 testReference = (Reference3)visualStudioProject.References.Item(index);
                            if ((testReference != null) && (testReference.Name == referenceName))
                            {
                                assemblyReference = testReference;
                                break;
                            }
                        }
                    }
                }

                AssemblyReferenceInformation assemblyReferenceInformation =
                    new AssemblyReferenceInformation(assemblyReference, documentHolder, xmlNamespaceManager, referenceNode);

                XmlNode embedNode = GetEmbedNode(referenceNode, xmlNamespaceManager);

                string assemblyPath = string.Empty;
                XmlNode hintNode = GetHintNode(referenceNode, xmlNamespaceManager);

                if (hintNode != null)
                {
                    // Hint node found -> assembly reference with a path to the assembly.
                    // This path could contain debug or release which needs to be changed to $(Configuration)
                    assemblyPath = hintNode.InnerText;

                    // Convert node to the correct path, if necessary
                    if (!string.IsNullOrEmpty(assemblyPath))
                    {
                        // check, if there is debug or release in the assembly path
                        string newAssemblyPath;
                        if (ReplaceDebugReleaseAgainstConfigurationPath(assemblyPath, out newAssemblyPath))
                        {
                            // Save fixed reference in xml document
                            SetHintPath(hintNode, newAssemblyPath);

                            // add xml document to the list projects which needs to be saved to fix the problem
                            AddToNeedsToBeSaved(documentHolder, projectFullName);

                            referenceIcon = ReferenceIcon.Error;
                        }
                    }
                    AddItemToListView(assemblyReferenceInformation, referenceIcon, projectGroup, assemblyPath, referenceName, embedNode);
                }
                else if (Settings.Default.ShowAllReferences)
                {
                    // No hint path means, that Visual Studio will try to resolve the path. There is no way for us to change that.
                    // It can also mean, that it is a project reference.
                    AddItemToListView(assemblyReferenceInformation, ReferenceIcon.CanNotChange, projectGroup, assemblyPath, referenceName, embedNode);
                }
            }
        }

        /// <summary>
        ///   Adds the assembly hints to list view.
        ///   The hints are read from the xxproj file, not from the object model!
        /// </summary>
        /// <param name = "currentProject">The current project.</param>
        /// <param name = "projectFullName">Full name of the project.</param>
        /// <param name = "projectGroup">The project group.</param>
        /// <param name = "visualStudioProject">The visual studio project.</param>
        private void AddAssemblyHintsToListView(Project currentProject, string projectFullName, ListViewGroup projectGroup, VSProject2 visualStudioProject)
        {
            XmlDocument projectDocument = new XmlDocument();
            projectDocument.Load(currentProject.FullName);

            XmlDocumentHolder documentHolder = new XmlDocumentHolder(projectDocument, projectFullName, currentProject);

            XmlNamespaceManager namespaceManager = new XmlNamespaceManager(projectDocument.NameTable);
            namespaceManager.AddNamespace(_namespaceShortCut, _msbuildNamespaceUri);

            XmlNodeList referenceNodelist = projectDocument.SelectNodes(_xpathReferences, namespaceManager);
            AddAssembliesToListView(documentHolder, namespaceManager, projectFullName, projectGroup, referenceNodelist, visualStudioProject);

            referenceNodelist = projectDocument.SelectNodes(_xpathProjectReferences, namespaceManager);
            AddAssembliesToListView(documentHolder, namespaceManager, projectFullName, projectGroup, referenceNodelist, visualStudioProject);
        }

        /// <summary>
        ///   Adds the item to list view.
        /// </summary>
        /// <param name = "assemblyReferenceInfomation">The assembly reference infomation.</param>
        /// <param name = "icon">The icon.</param>
        /// <param name = "projectGroup">The project group.</param>
        /// <param name = "assemblyPath">The assembly path.</param>
        /// <param name = "referenceName">Name of the reference.</param>
        private void AddItemToListView(AssemblyReferenceInformation assemblyReferenceInfomation, ReferenceIcon icon, ListViewGroup projectGroup, string assemblyPath, string referenceName, XmlNode embedNode)
        {
            Reference3 assemblyReference = assemblyReferenceInfomation.AssemblyReference;
            // Item name is the key to find existing entries in the list
            string itemKey = string.Format(CultureInfo.InvariantCulture, "{0}.{1}", projectGroup.Name, referenceName);

            if (string.IsNullOrEmpty(referenceName) || !_listViewHints.Items.ContainsKey(itemKey))
            {
                // if item does not exist, add it
                string copyLocalFlag = string.Empty;
                string version = string.Empty;
                string runtimeVersion = string.Empty;
                string strongName = string.Empty;
                string resolvedPath = "assembly not found";
                string specificVersionFlag = string.Empty;
                string autoReferenced = string.Empty;
                string embedNodeFlag = string.Empty;

                if (assemblyReference != null)
                {
                    if (embedNode != null)
                    {
                        embedNodeFlag = embedNode.InnerText;
                    }
                    else
                    {
                        embedNodeFlag = "False";
                    }
                    copyLocalFlag = assemblyReference.CopyLocal.ToString();
                    version = assemblyReference.Version;
                    runtimeVersion = assemblyReference.RuntimeVersion;
                    strongName = assemblyReference.StrongName.ToString();
                    resolvedPath = assemblyReference.Path;
                    specificVersionFlag = assemblyReference.SpecificVersion.ToString();
                    try
                    {
                        autoReferenced = assemblyReference.AutoReferenced.ToString();
                    }
                    catch
                    {
                        // a cobol projects fails in this case
                        autoReferenced = false.ToString();
                    }

                    bool sourceProjectReferenceExists = (assemblyReference.SourceProject != null);
                    if (!sourceProjectReferenceExists && string.IsNullOrEmpty(resolvedPath))
                    {
                        icon = ReferenceIcon.NotFound;
                    }
                    else if (sourceProjectReferenceExists)
                    {
                        if (assemblyReference.SourceProject.Kind == Constants.prjKindCSharpProject)
                        {
                            icon = ReferenceIcon.CSharpProject;
                        }
                        else if (assemblyReference.SourceProject.Kind == Constants.prjKindVBProject)
                        {
                            icon = ReferenceIcon.VisualBasicProject;
                        }
                        else if (assemblyReference.SourceProject.Kind == Constants.prjKindVSAProject)
                        {
                            icon = ReferenceIcon.CppProject;
                        }
                    }
                }

                ListViewItem item = new ListViewItem(new[] { referenceName, copyLocalFlag, specificVersionFlag, assemblyPath, resolvedPath, version, runtimeVersion, strongName, autoReferenced, embedNodeFlag }) { Tag = assemblyReferenceInfomation };

                // Set item appearance
                SetItemAppearance(item, icon);

                // assign item to the group
                item.Group = projectGroup;
                item.Name = itemKey;

                _listViewHints.Items.Add(item);
            }
        }

        /// <summary>
        ///   Adds to "needs to be saved" list
        /// </summary>
        /// <param name = "documentHolder">The document holder.</param>
        /// <param name = "projectFullName">Full name of the project.</param>
        private void AddToNeedsToBeSaved(XmlDocumentHolder documentHolder, string projectFullName)
        {
            if (!_needsToBeSaved.ContainsKey(projectFullName))
            {
                _needsToBeSaved.Add(projectFullName, documentHolder);
            }
        }

        /// <summary>
        ///   Clears the hint lists.
        /// </summary>
        private void ClearHintLists()
        {
            _needsToBeSaved.Clear();
            _listViewHints.Items.Clear();
        }

        /// <summary>
        ///   Enables all tool strip menu items.
        /// </summary>
        /// <param name = "enable">if set to <c>true</c> [enable].</param>
        private void EnableAllToolStripMenuItems(bool enable)
        {
            _toolStripMenuItemCopyLocal.Enabled = enable;
            _toolStripMenuItemSpecificVersion.Enabled = enable;
            _toolStripMenuItemCopyLocal.Checked = enable;
            _toolStripMenuItemSpecificVersion.Checked = enable;
            _toolStripMenuItemChangeProjectReferenceToAssemblyReference.Enabled = enable;
            _toolStripMenuItemEditHintPath.Enabled = enable;
        }

        /// <summary>
        ///   Gets the project group.
        /// </summary>
        /// <param name = "currentProject">The current project.</param>
        /// <returns></returns>
        private ListViewGroup GetProjectGroup(Project currentProject)
        {
            ListViewGroup projectGroup = new ListViewGroup(currentProject.Name, string.Format(CultureInfo.InvariantCulture, "Project: {0}", currentProject.Name));

            _listViewHints.Groups.Add(projectGroup);
            return projectGroup;
        }

        /// <summary>
        ///   Reads the project hints.
        /// </summary>
        private void ReadAllReferences()
        {
            ClearHintLists();

            if ((_applicationObject != null) && (_applicationObject.Solution != null))
            {
                // Walk through the projects of the solution and search for assembly references
                foreach (Project currentProject in _applicationObject.Solution.Projects)
                {
                    ReadProjectReferences(currentProject);
                }
            }
        }

        /// <summary>
        ///   Reads the project references.
        /// </summary>
        /// <param name = "currentProject">The current project.</param>
        private void ReadProjectReferences(Project currentProject)
        {
            try
            {
                if (currentProject != null)
                {
                    VSProject2 visualStudioProject = currentProject.Object as VSProject2;

                    // The current project can be a 'real' project, but it can also be a folder (see else if)
                    if (visualStudioProject != null)
                    {
                        string projectFullName = currentProject.FullName;

                        if (!string.IsNullOrEmpty(projectFullName))
                        {
                            FileInfo projectFileInfo = new FileInfo(projectFullName);

                            // If it is a csproj or a vbproj, add it the the list view
                            if (projectFileInfo.Exists &&
                                ((projectFileInfo.Extension == _csProjectFileExtension) ||
                                 (projectFileInfo.Extension == _vbProjectFileExtension) ||
                                 (projectFileInfo.Extension == _cobolProjectFileExtension)))
                            {
                                // Add a group for this project
                                ListViewGroup projectGroup = GetProjectGroup(currentProject);

                                AddAssemblyHintsToListView(currentProject, projectFullName, projectGroup, visualStudioProject);
                            }
                        }
                    }
                    else if ((currentProject.ProjectItems != null) && (currentProject.ProjectItems.Count > 0))
                    {
                        // Project Item Type  	 GUID
                        // Physical File         {6BB5F8EE-4483-11D3-8BCF-00C04F8EC28C}
                        // Physical Folder       {6BB5F8EF-4483-11D3-8BCF-00C04F8EC28C}
                        // Virtual Folder        {6BB5F8F0-4483-11D3-8BCF-00C04F8EC28C}
                        // Subproject            {EA6618E8-6E24-4528-94BE-6889FE16485C}

                        // The projects contains a sub folder -> search for projects in these folders
                        foreach (ProjectItem currentProjectItem in currentProject.ProjectItems)
                        {
                            if (currentProjectItem.SubProject != null)
                            {
                                ReadProjectReferences(currentProjectItem.SubProject);
                            }
                        }
                    }
                }
                // Enable fixit button, if there is something to fix
                toolStripButtonFixIt.Enabled = (_needsToBeSaved.Count > 0);
            }
            catch (Exception ex)
            {
                ShowMessage(ex);
            }
        }

        /// <summary>
        ///   Registers the events.
        /// </summary>
        private void RegisterEvents()
        {
            if (_solutionEvents != null)
            {
                UnregisterEvents();
            }
            _solutionEvents = _applicationObject.Events.SolutionEvents;

            // register new events
            _solutionEvents.Opened += SolutionEventsOpened;
            _solutionEvents.ProjectAdded += SolutionEventsProjectAdded;
            _solutionEvents.ProjectRemoved += SolutionEventsProjectRemoved;
            _solutionEvents.ProjectRenamed += SolutionEventsProjectRenamed;
            _solutionEvents.AfterClosing += SolutionEventsAfterClosing;
            _documentEvents.DocumentSaved += DocumentEventsDocumentSaved;

            _commandEvents.AfterExecute += CommandEventsAfterExecute;
        }

        /// <summary>
        ///   Saves the dirty projects.
        /// </summary>
        private void SaveDirtyProjects()
        {
            try
            {
                foreach (KeyValuePair<string, XmlDocumentHolder> project in _needsToBeSaved)
                {
                    XmlDocumentHolder documentHolder = project.Value;

                    SaveProject(documentHolder);
                }
            }
            catch (Exception ex)
            {
                ShowMessage(ex);
            }
        }

        /// <summary>
        ///   Saves the project.
        /// </summary>
        /// <param name = "documentHolder">The document holder.</param>
        private void SaveProject(XmlDocumentHolder documentHolder)
        {
            string projectName = documentHolder.Project.FullName;

            // Check out the project
            if (!documentHolder.Project.Saved)
            {
                MessageBox.Show(CommandBar.ToolWindowControl_SaveProject_Please_save_all_projects_before_you_fix_the_problems_);
                return;
            }

            if ((_sourceControl != null) && (_sourceControl.IsItemUnderSCC(projectName)) && (!_sourceControl.IsItemCheckedOut(projectName)))
            {
                _sourceControl.CheckOutItem(projectName);
            }

            // Check the readonly flag
            FileInfo fi = new FileInfo(documentHolder.ProjectFileName);
            if ((fi.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
            {
                File.SetAttributes(documentHolder.ProjectFileName,
                                   File.GetAttributes(documentHolder.ProjectFileName) &
                                   ~(FileAttributes.Archive | FileAttributes.ReadOnly));
            }

            using (XmlTextWriter writer = new XmlTextWriter(documentHolder.ProjectFileName, Encoding.UTF8))
            {
                writer.Formatting = Formatting.Indented;
                documentHolder.XmlDocument.Save(writer);
            }
        }

        /// <summary>
        ///   _commands the events_ after execute.
        /// </summary>
        /// <param name = "guid">The GUID.</param>
        /// <param name = "id">The ID.</param>
        /// <param name = "customIn">The custom in.</param>
        /// <param name = "customOut">The custom out.</param>
        private void CommandEventsAfterExecute(string guid, int id, object customIn, object customOut)
        {
            //Command name: File.SaveSelectedItems
            //Command GUID/ID: {5EFC7975-14BC-11CF-9B2B-00AA00573819}, 331

            //Command name: File.SaveAll
            //Command GUID/ID: {5EFC7975-14BC-11CF-9B2B-00AA00573819}, 224

            //Command name: File.SaveSelectedItemsAs
            //Command GUID/ID: {5EFC7975-14BC-11CF-9B2B-00AA00573819}, 226

            //Command name: Build.SolutionConfigurations
            //Command GUID/ID: {5EFC7975-14BC-11CF-9B2B-00AA00573819}, 684

            //Command name: Project.Addreference
            //Command GUID/ID: {1496A755-94DE-11D0-8C3F-00C04FC2AAE2}, 1113

            //Command name: Build.SolutionConfigurations is not working correctly in VS 2008
            //if (((Guid == "{5EFC7975-14BC-11CF-9B2B-00AA00573819}") && (ID == 331)) ||
            //    ((Guid == "{5EFC7975-14BC-11CF-9B2B-00AA00573819}") && (ID == 224)) ||
            //    ((Guid == "{5EFC7975-14BC-11CF-9B2B-00AA00573819}") && (ID == 226)) ||
            //    ((Guid == "{5EFC7975-14BC-11CF-9B2B-00AA00573819}") && (ID == 684)))
            if (((guid == "{5EFC7975-14BC-11CF-9B2B-00AA00573819}") && (id == 331)) ||
                ((guid == "{5EFC7975-14BC-11CF-9B2B-00AA00573819}") && (id == 224)) ||
                ((guid == "{5EFC7975-14BC-11CF-9B2B-00AA00573819}") && (id == 226)))
            {
                ReadAllReferences();
            }
            else if ((guid == "{1496A755-94DE-11D0-8C3F-00C04FC2AAE2}") && (id == 1113))
            {
                if (ParentToolWindow != null)
                {
                    ((IVsWindowFrame)ParentToolWindow.Frame).Show();
                }
            }
        }

        /// <summary>
        ///   _documents the events_ document saved.
        /// </summary>
        /// <param name = "document">The document.</param>
        private void DocumentEventsDocumentSaved(Document document)
        {
            ReadAllReferences();
        }

        /// <summary>
        ///   _solutions the events_ after closing.
        /// </summary>
        private void SolutionEventsAfterClosing()
        {
            ClearHintLists();
        }

        /// <summary>
        ///   Solution_s the opened.
        /// </summary>
        private void SolutionEventsOpened()
        {
            ReadAllReferences();
        }

        /// <summary>
        ///   Solution_s the project added.
        /// </summary>
        /// <param name = "project">The project.</param>
        private void SolutionEventsProjectAdded(Project project)
        {
            ReadAllReferences();
        }

        /// <summary>
        ///   _solutions the events_ project removed.
        /// </summary>
        /// <param name = "project">The project.</param>
        private void SolutionEventsProjectRemoved(Project project)
        {
            ReadAllReferences();
        }

        /// <summary>
        ///   _solutions the events_ project renamed.
        /// </summary>
        /// <param name = "project">The project.</param>
        /// <param name = "oldName">The old name.</param>
        private void SolutionEventsProjectRenamed(Project project, string oldName)
        {
            ReadAllReferences();
        }

        #endregion

        #region Nested type: ReferenceIcon

        /// <summary>
        /// </summary>
        private enum ReferenceIcon
        {
            Error,
            Ok,
            NotFound,
            CanNotChange,
            VisualBasicProject,
            CSharpProject,
            CppProject
        }

        #endregion
    }
}